/*****************************************************************************
 * gmtm.c: Main functions
 *****************************************************************************
 * Copyright (C) 2010 Sam Grandjean
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/stat.h>
#include <signal.h>

#include "gmtm.h"
#include "gd.h"
#include "img.h"
#include "args.h"
#include "util.h"
#include "version.h"

// Fixes compiler warning "implicit declaration of function ‘round’"
double round(double x);

/* ************************************************************************* */

static char out_tmp[] = ".tmp-gmtm-XXXXXX";

void printUsage(char **argv)
{
	printf("Usage: %s [OPTION]... IMAGE\n", argv[0]);
	printf("Generate tilesets for Google Maps™ mapping service.\n");
	printf("IMAGE must be a file in PNG, JPEG or GIF format.\n");
	printf("\n");
	printf("  -f, --format=[p|j|g]     tiles format (see FORMAT below)\n");
	printf("  -c, --color=[p|t]        color depth of tiles (see COLOR below)\n");
	printf("  -i, --interlaced         set interlace bit to tiles\n");
	printf("  -b, --background=R,G,B   set the tiles background color (0-255 triplet)\n");
	printf("  -m, --magnification=N    set the zoom levels magnification (default: %d)\n", GOOGLEMAPS_DEFAULT_MAGNIFICATION);
	printf("  -t, --tilesize=N         set tile size (default: %d)\n", GOOGLEMAPS_DEFAULT_TILE_SIZE);
	printf("  -q, --quiet              do not display messages\n");
	printf("  -h, --help               print this help and exit\n");
	printf("      --version            output version information and exit\n");
	printf("\n");
	printf("FORMAT must be one of the following: p[ng] | j[peg] | g[if]\n");
	printf("COLOR must be one of the following: p[alette] | t[ruecolor]\n");
}

void printVersion()
{
	printf("Google Maps™ Tileset Maker version %s\n", VERSION);
	printf("Copyright © 2010 Sam Grandjean\n");
	printf("License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n");
	printf("This is free software: you are free to change and redistribute it.\n");
	printf("There is NO WARRANTY, to the extent permitted by law.\n");
	printf("Written by Sam Grandjean\n");
}

int getGoogleMapZoom(const int width, const int height, const int tilesize)
{
	int tiles;
	int zoom;
	if ((tiles = (int) ceil((float) max(width, height) / (float) tilesize)) == 0) {
		return 0;
	}
	for (zoom = GOOGLEMAPS_MIN_ZOOM; zoom <= GOOGLEMAPS_MAX_ZOOM; zoom++) {
		if (tiles <= pow(2, zoom)) {
			return zoom;
		}
	}
	return 0;
}

void signalHandler(const int signo)
{
	fprintf(stderr, "%s\n", strsignal(signo));

	if (access(out_tmp, F_OK) == 0) {
		remove(out_tmp);
	}

	exit(2);
}

/* ************************************************************************* */

int main(int argc, char **argv)
{
	struct Config config = {
		'0', // must be changed later
		-1,
		0,
		{0, 0, 0},
		GOOGLEMAPS_DEFAULT_MAGNIFICATION,
		GOOGLEMAPS_DEFAULT_TILE_SIZE,
		0
	};
	char src_path[255];
	char src_ext[16];
	enum IMAGE_FORMAT src_format;
	int src_width;
	int src_height;
	int src_truecolor;

	gdImagePtr im = NULL;

	char out_dir[255] = "";
	char out_path[255];
	int out_truecolor;
	int out_interlace;
	enum IMAGE_FORMAT out_format;

	int zoom;
	float magnitude;
	int tile_x, tile_y;

	int current_width, current_height;
	int tiles_x, tiles_y;
	gdImagePtr tile_im = NULL;

	int tileset_files;
	double tileset_bench;
	int total_files = 0;
	double total_bench = utime(NULL);



	/* Initialization */

	if (mkstemp(out_tmp) == -1) {
		fprintf(stderr, "Error: mkstemp failed\n");
		return 1;
	}

	signal(SIGINT, signalHandler); // 2
	signal(SIGTERM, signalHandler); // 15



	/* Parse arguments */

	switch (parseArgs(argc, argv, &config, src_path, sizeof(src_path))) {
		case 0:
			break;
		case -1:
			printUsage(argv);
			return 0;
		case -2:
			printVersion();
			return 0;
		default:
			fprintf(stderr, "Try ‘%s --help’ or ‘man %s’ for more information.\n", argv[0], basename(argv[0]));
			return 1;
	}

	if (access(src_path, R_OK) != 0) {
		fprintf(stderr, "Error: file ‘%s’ does not exist or is not readable\n", src_path);
		return 1;
	}

	strncpy(src_ext, str_tolower(file_ext(src_path)), sizeof(src_ext));
	if (strcmp(src_ext, "png") == 0) {
		src_format = PNG;
	} else if (strcmp(src_ext, "jpg") == 0 || strcmp(src_ext, "jpeg") == 0) {
		src_format = JPEG;
	} else if (strcmp(src_ext, "gif") == 0) {
		src_format = GIF;
	} else {
		fprintf(stderr, "Error: invalid file format\n");
		return 1;
	}



	/* Create output directory */

	strncpy(out_dir, basename(src_path), strlen(basename(src_path)) - strlen(src_ext) - 1);
	if (access(out_dir, F_OK) == 0) {
		if (access(out_dir, W_OK) != 0) {
			fprintf(stderr, "Error: output directory ‘%s’ is not writable\n", out_dir);
			return 1;
		}
		if (!config.quiet) printf("Notice: Output directory ‘%s’ already exists\n", out_dir);
	} else {
		if (mkdir(out_dir, 0755) != 0) {
			fprintf(stderr, "Error: cannot create output directory ‘%s’\n", out_dir);
			return 1;
		}
	}



	/* Open image */

	if (!config.quiet) printf("Opening ‘%s’ (%s)...", basename(src_path), bytes_format(file_size(src_path)));
	if (!config.quiet) fflush(stdout);

	if ((im = load_image(src_path, src_format)) == NULL) {
		if (!config.quiet) printf("\n");
		fprintf(stderr, "Error: invalid image or image is not readable\n");
		return 1;
	}
	src_width = gdImageSX(im);
	src_height = gdImageSY(im);
	src_truecolor = gdImageTrueColor(im);

	if (!config.quiet) printf(" %s %s %d×%d\n",
		(src_format == PNG ? "PNG" : (src_format == JPEG ? "JPEG" : "GIF")),
		(src_truecolor == 0 ? "8 bit" : "24 bit"),
		src_width, src_height
	);



	/* Output parameters */

	out_truecolor = (config.truecolor == -1 ? src_truecolor : config.truecolor);
	out_interlace = config.interlace;
	out_format = (config.format == '0' ? src_format : (config.format == 'p' ? PNG : (config.format == 'j' ? JPEG : GIF)));

	// Force palette for GIF
	if (out_format == GIF) out_truecolor = 0;



	/* Loop each zoom levels */

	for (
		zoom = getGoogleMapZoom(src_width, src_height, config.tilesize), magnitude = 1;
		zoom >= GOOGLEMAPS_MIN_ZOOM;
		zoom--, magnitude /= (float) config.magnification
	) {
		if (!config.quiet) printf("Generating tileset (zoom: %d, magnitude: %.3f)\n", zoom, magnitude);

		tileset_files = 0;
		tileset_bench = utime(NULL);

		current_width = (int) round((float) src_width * magnitude);
		current_height = (int) round((float) src_height * magnitude);


		/* Create zoom directory */

		sprintf(out_path, "%s/%d", out_dir, zoom);
		if (access(out_path, F_OK) == 0) {
			if (access(out_path, W_OK) != 0) {
				fprintf(stderr, "Warning: output directory ‘%s’ is not writable\n", out_path);
				continue;
			}
		} else {
			if (mkdir(out_path, 0755) != 0) {
				fprintf(stderr, "Warning: cannot create output directory ‘%s’\n", out_path);
				continue;
			}
		}


		/* Resize image */

		if (magnitude != 1) {
			if (!config.quiet) printf("\tResizing image (%d×%d), this may take a while...", current_width, current_height);
			if (!config.quiet) fflush(stdout);
			im = resize_image_replace(im, current_width, current_height, out_truecolor);
			if (!config.quiet) printf("\n");
		}


		/* Split tiles */

		tiles_x = (int) ceil((float) current_width / (float) config.tilesize);
		tiles_y = (int) ceil((float) current_height / (float) config.tilesize);

		if (!config.quiet) printf("\tSplitting...");
		if (!config.quiet) fflush(stdout);

		for (tile_y = 0; tile_y < tiles_y; tile_y++) {
			for (tile_x = 0; tile_x < tiles_x; tile_x++) {
				sprintf(out_path, "%s/%d/%d_%d_%d.%s",
				  out_dir, zoom, zoom, tile_x, tile_y,
				  (out_format == PNG ? "png" : (out_format == JPEG ? "jpg" : "gif")));

				if (out_truecolor == 0) {
					tile_im = gdImageCreate(config.tilesize, config.tilesize);
				} else {
					tile_im = gdImageCreateTrueColor(config.tilesize, config.tilesize);
				}
				gdImageCopy(tile_im, im, 0, 0, tile_x * config.tilesize, tile_y * config.tilesize, config.tilesize, config.tilesize);
				gdImageInterlace(tile_im, out_interlace);
				if (save_image(tile_im, out_tmp, out_format) == 0) {
					rename(out_tmp, out_path);
					chmod(out_path, 0644);
				}
				gdImageDestroy(tile_im);

				tileset_files++;
				total_files++;
			}
		}
		if (!config.quiet) printf("\n\tGenerated %d %s in %.2f seconds\n", tileset_files, (tileset_files > 1 ? "files" : "file"), bench(tileset_bench, NULL));
	}



	/* Close image */

	gdImageDestroy(im);



	/* Print result */

	if (!config.quiet) printf("%d %s (%s %s%s) generated in %.2f seconds\n",
	  total_files, (total_files > 1 ? "files" : "file"),
	  (out_format == PNG ? "PNG" : (out_format == JPEG ? "JPEG" : "GIF")),
	  (out_truecolor == 0 ? "8 bit" : "24 bit"),
	  (out_interlace == 0 ? "" : " interlaced"),
	  bench(total_bench, NULL)
	);

	return 0;
}

